package com.zyf.project.kvdb.client.core.impl;

import com.zyf.project.kvdb.client.core.KVDBOperator;
import com.zyf.project.kvdb.protocol.constants.Constants;
import com.zyf.project.kvdb.protocol.exception.KVDBException;
import com.zyf.project.kvdb.protocol.exception.KVDBTimeoutException;
import com.zyf.project.kvdb.protocol.handler.impl.NettyClient;
import com.zyf.project.kvdb.protocol.proto.Message;
import com.zyf.project.kvdb.protocol.proto.Response;
import com.zyf.project.kvdb.protocol.proto.impl.KVDBMessage;
import utils.binary.BytesUtils;
import utils.binary.impl.Bytes;

/**
 * <h1>单例数据库操作</h1>
 *
 * @author Zhou Yifan
 */
public class KVDBSingle implements KVDBOperator {

    /**
     * 当前数据库的连接，{@link NettyClient}
     */
    private final NettyClient client;

    public KVDBSingle(NettyClient client) {
        this.client = client;
    }

    /**
     * 发送消息，利用 promise 等待实现同步获取响应，调用了 {@link NettyClient#send(Message)} 方法
     *
     * @param message 消息，{@link Message}
     * @return 响应，{@link Response}
     */
    private Response send(Message message) {
        return client.send(message);
    }

    /**
     * 发送消息，调用了 {@link NettyClient#sendAsync(Message)}方法
     *
     * @param message 消息，{@link Message}
     * @return 响应，{@link Response}
     */
    private boolean sendAsync(Message message) {
        return client.sendAsync(message);
    }

    /**
     * 存在性查询
     *
     * @param key 待查询的键
     * @return 布尔值
     * @throws KVDBException KVDB 可能存在的异常
     */
    @Override
    public boolean exists(Bytes key) throws KVDBException {
        Response response = send(KVDBMessage.exists(key));
        if (null == response) {
            throw new KVDBTimeoutException("time out");
        } else if (response.getCode() == Constants.ERROR) {
            throw new KVDBException(BytesUtils.toString(response.getResult()[0].toBytes()));
        }
        return BytesUtils.toInt(response.getResult()[0].toBytes()) == 1;
    }

    /**
     * 存在性查询，支持多个键
     *
     * @param keys 待查询的键
     * @return 布尔值
     * @throws KVDBException KVDB 可能存在的异常
     */
    @Override
    public boolean[] exists(Bytes... keys) throws KVDBException {
        Response response = send(KVDBMessage.exists(keys));
        if (null == response) {
            throw new KVDBTimeoutException("time out");
        } else if (response.getCode() == Constants.ERROR) {
            throw new KVDBException(BytesUtils.toString(response.getResult()[0].toBytes()));
        }
        boolean[] results = new boolean[keys.length];
        for (int i = 0; i < keys.length; i++) {
            results[i] = BytesUtils.toInt(response.getResult()[i].toBytes()) == 1;
        }
        return results;
    }

    /**
     * 键值对获取
     *
     * @param key 键，{@link Bytes} 类型
     * @return 键对应的值，{@link Bytes} 类型
     * @throws KVDBException KVDB 可能存在的异常
     */
    @Override
    public Bytes get(Bytes key) throws KVDBException {
        Response response = send(KVDBMessage.get(key));
        if (null == response) {
            throw new KVDBTimeoutException("time out");
        } else if (response.getCode() == Constants.ERROR) {
            throw new KVDBException(BytesUtils.toString(response.getResult()[0].toBytes()));
        }

        return response.getResult()[0];
    }

    /**
     * 键值对获取
     *
     * @param keys 键，{@link Bytes} 类型数组
     * @return 键对应的值，{@link Bytes} 类型数组
     * @throws KVDBException KVDB 可能存在的异常
     */
    @Override
    public Bytes[] get(Bytes... keys) throws KVDBException {
        Response response = send(KVDBMessage.get(keys));
        if (null == response) {
            throw new KVDBTimeoutException("time out");
        } else if (response.getCode() == Constants.ERROR) {
            throw new KVDBException(BytesUtils.toString(response.getResult()[0].toBytes()));
        }

        return response.getResult();
    }

    /**
     * 设置键值对（添加）
     *
     * @param key   键
     * @param value 值
     * @return 布尔值
     * @throws KVDBException KVDB 可能存在的异常
     */
    @Override
    public boolean put(Bytes key, Bytes value) throws KVDBException {
        return put(key, value, false);
    }

    /**
     * 设置键值对（添加）
     *
     * @param key   键
     * @param value 值
     * @param async 是否异步（Promise 消息）
     * @return 布尔值
     * @throws KVDBException KVDB 可能存在的异常
     */
    @Override
    public boolean put(Bytes key, Bytes value, boolean async) throws KVDBException {
        Message message = KVDBMessage.put(key, value);
        if (async) {
            return sendAsync(message);
        }
        Response response = send(message);
        if (null == response) {
            throw new KVDBTimeoutException("time out");
        } else if (response.getCode() == Constants.ERROR) {
            throw new KVDBException(BytesUtils.toString(response.getResult()[0].toBytes()));
        }
        return false;
    }

    /**
     * 开启批处理
     *
     * @return 布尔值
     * @throws KVDBException KVDB 可能存在的异常
     */
    @Override
    public boolean batchBegin() throws KVDBException {
        Response response = send(KVDBMessage.batchBegin());
        if (null == response) {
            throw new KVDBTimeoutException("time out");
        } else if (response.getCode() == Constants.ERROR) {
            throw new KVDBException(BytesUtils.toString(response.getResult()[0].toBytes()));
        }

        return true;
    }

    /**
     * 取消批处理
     *
     * @return 布尔值
     * @throws KVDBException KVDB 可能存在的异常
     */
    @Override
    public boolean batchAbort() throws KVDBException {
        Response response = send(KVDBMessage.batchAbort());
        if (null == response) {
            throw new KVDBTimeoutException("time out");
        } else if (response.getCode() == Constants.ERROR) {
            throw new KVDBException(BytesUtils.toString(response.getResult()[0].toBytes()));
        }

        return true;
    }

    /**
     * 提交批处理，服务器掉线重连后会丢失未提交的数据，未提交的 batch 对其他客户端连接而言是不可见的
     *
     * @return 布尔值
     * @throws KVDBException KVDB 可能存在的异常
     */
    @Override
    public boolean batchCommit() throws KVDBException {
        Response response = send(KVDBMessage.batchCommit());
        if (null == response) {
            throw new KVDBTimeoutException("time out");
        } else if (response.getCode() == Constants.ERROR) {
            throw new KVDBException(BytesUtils.toString(response.getResult()[0].toBytes()));
        }

        return true;
    }

    @Override
    public boolean batchCommit(long size) throws KVDBException {
        Response response = send(KVDBMessage.batchCommit(Bytes.fromLong(size)));
        if (null == response) {
            throw new KVDBTimeoutException("time out");
        } else if (response.getCode() == Constants.ERROR) {
            throw new KVDBException(BytesUtils.toString(response.getResult()[0].toBytes()));
        }

        return true;
    }

    /**
     * 关闭数据库
     */
    @Override
    public void close() {
        client.stop();
    }
}
